package com.cheung.shopkeeper.common.utils;

import java.io.ByteArrayOutputStream;
import java.io.StringReader;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.namespace.QName;
import javax.xml.transform.stream.StreamSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.sun.xml.bind.marshaller.NamespacePrefixMapper;

public class JAXBUtil {
	
	private static final Log logger = LogFactory.getLog(JAXBUtil.class);
	
	//key为packageName, value为JAXBContext
	private static Map<String, JAXBContext> jaxbContextHolder = new HashMap<String, JAXBContext>();
	
	private static JAXBContext getJaxbContext(String packageName) throws JAXBException {
		JAXBContext inst = jaxbContextHolder.get(packageName);
		if (inst == null) {
			inst = JAXBContext.newInstance(packageName);
			jaxbContextHolder.put(packageName, inst);
			return inst;
		}
		return inst;
	}
	
	public static <T> T formXML(Class<T> clazz, String message) {
		JAXBContext context;
		Unmarshaller u;
		JAXBElement<T> element;
		try {
			context = getJaxbContext(clazz.getPackage().getName());
			u = context.createUnmarshaller();
			//element = (JAXBElement<T>) u.unmarshal(new StreamSource(new StringReader(message)));
			element = u.unmarshal(new StreamSource(new StringReader(message)), clazz);
			
		} catch (JAXBException e) {
			logger.error("error when unmarshalling from a xml string");
			throw new RuntimeException(e);
		}
		return element.getValue();
		
	}
	
	@SuppressWarnings("unchecked")
	public static <T> String toXML(Object target, String encoding, String schemaLocation,
			QName qName, NamespacePrefixMapper npMapper) {
		try {
			
			JAXBContext context = getJaxbContext(target.getClass().getPackage().getName());
			
			JAXBElement<T> element = new JAXBElement(qName, target.getClass(), null, target);
			Marshaller m = context.createMarshaller();
			m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
			if (encoding == null)
				encoding = "UTF-8";
			
			m.setProperty(Marshaller.JAXB_ENCODING, encoding);
			
			if (schemaLocation != null)
				m.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, schemaLocation);
			m.setProperty("com.sun.xml.bind.namespacePrefixMapper", npMapper);
			
			ByteArrayOutputStream ot = new ByteArrayOutputStream();
			m.marshal(element, ot);
			byte[] buf = ot.toByteArray();
			return new String(buf, 0, buf.length, encoding);
		} catch (Exception e) {
			logger.error("error when create an element for marshalling", e);
		}
		return null;
	}
	
	@SuppressWarnings("unchecked")
	public static <T> String toXML(Object target, String schemaLocation, QName qName,
			NamespacePrefixMapper npMapper) {
		try {
			
			JAXBContext context = getJaxbContext(target.getClass().getPackage().getName());
			
			JAXBElement<T> element = new JAXBElement(qName, target.getClass(), null, target);
			Marshaller m = context.createMarshaller();
			m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
			m.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
			if (schemaLocation != null)
				m.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, schemaLocation);
			m.setProperty("com.sun.xml.bind.namespacePrefixMapper", npMapper);
			
			ByteArrayOutputStream ot = new ByteArrayOutputStream();
			m.marshal(element, ot);
			byte[] buf = ot.toByteArray();
			return new String(buf, 0, buf.length, "UTF-8");
		} catch (Exception e) {
			String errorMessage = "error when create an element for marshalling";
			logger.error(errorMessage);
			logger.error(e);
		}
		return null;
	}
	
	public static Date fromXMLCalendar(XMLGregorianCalendar xmlCalendar) {
		if (xmlCalendar == null)
			return null;
		Calendar cal = xmlCalendar.toGregorianCalendar();
		return cal.getTime();
	}
	
	public static XMLGregorianCalendar toXMLCalendar(Date date) {
		if (date == null)
			return null;
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		
		XMLGregorianCalendar cal = null;
		try {
			cal = DatatypeFactory.newInstance().newXMLGregorianCalendar();
			cal.setYear(calendar.get(Calendar.YEAR));
			cal.setMonth(calendar.get(Calendar.MONTH) + 1);
			cal.setDay(calendar.get(Calendar.DAY_OF_MONTH));
			cal.setHour(calendar.get(Calendar.HOUR_OF_DAY));
			cal.setMinute(calendar.get(Calendar.MINUTE));
			cal.setSecond(calendar.get(Calendar.SECOND));
		} catch (DatatypeConfigurationException e) {
			logger.error("error when generate XML Calendar");
			throw new RuntimeException(e);
		}
		return cal;
	}
	
	public static void main(String... s) {
		XMLGregorianCalendar cal = JAXBUtil.toXMLCalendar(new Date());
		System.out.print(cal.getXMLSchemaType());
	}
}
